// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "logical_buffer_collection.h"

#include <lib/image-format/image_format.h>
#include <limits.h>  // PAGE_SIZE
#include <zircon/assert.h>

#include <limits>  // std::numeric_limits

#include "buffer_collection.h"
#include "buffer_collection_token.h"
#include "koid_util.h"
#include "usage_pixel_format_cost.h"

namespace {

// Sysmem is creating the VMOs, so sysmem can have all the rights and just not
// mis-use any rights.  Remove ZX_RIGHT_EXECUTE though.
const uint32_t kSysmemVmoRights = ZX_DEFAULT_VMO_RIGHTS & ~ZX_RIGHT_EXECUTE;
// 1 GiB cap for now.
const uint64_t kMaxTotalSizeBytesPerCollection = 1ull * 1024 * 1024 * 1024;
// 256 MiB cap for now.
const uint64_t kMaxSizeBytesPerBuffer = 256ull * 1024 * 1024;

// Zero-initialized, so it shouldn't take up space on-disk.
constexpr uint64_t kFlushThroughBytes = 8192;
const uint8_t kZeroes[kFlushThroughBytes] = {};

template <typename T>
bool IsNonZeroPowerOf2(T value) {
  if (!value) {
    return false;
  }
  if (value & (value - 1)) {
    return false;
  }
  return true;
}

// TODO(dustingreen): Switch to FIDL C++ generated code (preferred) and remove
// this, or fully implement something like this for all fields that need 0 to
// imply a default value that isn't 0.
template <typename T>
void FieldDefault1(T* value) {
  if (*value == 0) {
    *value = 1;
  }
}

template <typename T>
void FieldDefaultMax(T* value) {
  if (*value == 0) {
    *value = std::numeric_limits<T>::max();
  }
}

// This exists just to document the meaning for now, to make the conversion more
// clear when we switch from FIDL struct to FIDL table.
template <typename T>
void FieldDefaultZero(T* value) {
  // no-op
}

template <typename T>
T AlignUp(T value, T divisor) {
  return (value + divisor - 1) / divisor * divisor;
}

bool IsCpuUsage(const fuchsia_sysmem_BufferUsage& usage) { return usage.cpu != 0; }

}  // namespace

// static
void LogicalBufferCollection::Create(zx::channel buffer_collection_token_request,
                                     Device* parent_device) {
  fbl::RefPtr<LogicalBufferCollection> logical_buffer_collection =
      fbl::AdoptRef<LogicalBufferCollection>(new LogicalBufferCollection(parent_device));
  // The existence of a channel-owned BufferCollectionToken adds a
  // fbl::RefPtr<> ref to LogicalBufferCollection.
  LogInfo("LogicalBufferCollection::Create()");
  logical_buffer_collection->CreateBufferCollectionToken(
      logical_buffer_collection, std::numeric_limits<uint32_t>::max(),
      std::move(buffer_collection_token_request));
}

// static
//
// The buffer_collection_token is the client end of the BufferCollectionToken
// which the client is exchanging for the BufferCollection (which the client is
// passing the server end of in buffer_collection_request).
//
// However, before we convert the client's token into a BufferCollection and
// start processing the messages the client may have already sent toward the
// BufferCollection, we want to process all the messages the client may have
// already sent toward the BufferCollectionToken.  This comes up because the
// BufferCollectionToken and Allocator2 are separate channels.
//
// We know that fidl_server will process all messages before it processes the
// close - it intentionally delays noticing the close until no messages are
// available to read.
//
// So this method will close the buffer_collection_token and when it closes via
// normal FIDL processing path, the token will remember the
// buffer_collection_request to essentially convert itself into.
void LogicalBufferCollection::BindSharedCollection(Device* parent_device,
                                                   zx::channel buffer_collection_token,
                                                   zx::channel buffer_collection_request) {
  ZX_DEBUG_ASSERT(buffer_collection_token);
  ZX_DEBUG_ASSERT(buffer_collection_request);

  zx_koid_t token_client_koid;
  zx_koid_t token_server_koid;
  zx_status_t status =
      get_channel_koids(buffer_collection_token, &token_client_koid, &token_server_koid);
  if (status != ZX_OK) {
    LogError("Failed to get channel koids");
    // ~buffer_collection_token
    // ~buffer_collection_request
    return;
  }

  BufferCollectionToken* token = parent_device->FindTokenByServerChannelKoid(token_server_koid);
  if (!token) {
    // The most likely scenario for why the token was not found is that Sync() was not called on
    // either the BufferCollectionToken or the BufferCollection.
    LogError("Could not find token by server channel koid");
    // ~buffer_collection_token
    // ~buffer_collection_request
    return;
  }

  // This will token->FailAsync() if the token has already got one, or if the
  // token already saw token->Close().
  token->SetBufferCollectionRequest(std::move(buffer_collection_request));

  // At this point, the token will process the rest of its previously queued
  // messages (from client to server), and then will convert the token into
  // a BufferCollection (view).  That conversion happens async shortly in
  // BindSharedCollectionInternal() (unless the LogicalBufferCollection fails
  // before then, in which case everything just gets deleted).
  //
  // ~buffer_collection_token here closes the client end of the token, but we
  // still process the rest of the queued messages before we process the
  // close.
  //
  // ~buffer_collection_token
}

void LogicalBufferCollection::CreateBufferCollectionToken(
    fbl::RefPtr<LogicalBufferCollection> self, uint32_t rights_attenuation_mask,
    zx::channel buffer_collection_token_request) {
  auto token = BufferCollectionToken::Create(parent_device_, self, rights_attenuation_mask);
  token->SetErrorHandler([this, token_ptr = token.get()](zx_status_t status) {
    // Clean close from FIDL channel point of view is ZX_ERR_PEER_CLOSED,
    // and ZX_OK is never passed to the error handler.
    ZX_DEBUG_ASSERT(status != ZX_OK);

    // We know |this| is alive because the token is alive and the token has
    // a fbl::RefPtr<LogicalBufferCollection>.  The token is alive because
    // the token is still in token_views_.
    //
    // Any other deletion of the token_ptr out of token_views_ (outside of
    // this error handler) doesn't run this error handler.
    //
    // TODO(dustingreen): Switch to contains() when C++20.
    ZX_DEBUG_ASSERT(token_views_.find(token_ptr) != token_views_.end());

    zx::channel buffer_collection_request = token_ptr->TakeBufferCollectionRequest();

    if (!(status == ZX_ERR_PEER_CLOSED && (token_ptr->is_done() || buffer_collection_request))) {
      // We don't have to explicitly remove token from token_views_
      // because Fail() will token_views_.clear().
      //
      // A token whose error handler sees anything other than clean close
      // with is_done() implies LogicalBufferCollection failure.  The
      // ability to detect unexpected closure of a token is a main reason
      // we use a channel for BufferCollectionToken instead of an
      // eventpair.
      //
      // If a participant for some reason finds itself with an extra token it doesn't need, the
      // participant should use Close() to avoid triggering this failure.
      Fail("Token failure causing LogicalBufferCollection failure - status: %d", status);
      return;
    }

    // At this point we know the token channel was closed cleanly, and that
    // before the client's closing the channel, the client did a
    // token::Close() or allocator::BindSharedCollection().
    ZX_DEBUG_ASSERT(status == ZX_ERR_PEER_CLOSED &&
                    (token_ptr->is_done() || buffer_collection_request));
    // BufferCollectionToken enforces that these never both become true; the
    // BufferCollectionToken will fail instead.
    ZX_DEBUG_ASSERT(!(token_ptr->is_done() && buffer_collection_request));

    if (!buffer_collection_request) {
      // This was a token::Close().  In this case we want to stop tracking the token now that we've
      // processed all its previously-queued inbound messages.  This might be the last token, so we
      // MaybeAllocate().  This path isn't a failure (unless there are also zero BufferCollection
      // views in which case MaybeAllocate() calls Fail()).
      auto self = token_ptr->parent_shared();
      ZX_DEBUG_ASSERT(self.get() == this);
      token_views_.erase(token_ptr);
      MaybeAllocate();
      // ~self may delete "this"
    }

    // At this point we know that this was a BindSharedCollection().  We
    // need to convert the BufferCollectionToken into a BufferCollection.
    //
    // ~token_ptr during this call
    BindSharedCollectionInternal(token_ptr, std::move(buffer_collection_request));
  });
  auto token_ptr = token.get();
  token_views_.insert({token_ptr, std::move(token)});

  zx_koid_t server_koid;
  zx_koid_t client_koid;
  zx_status_t status =
      get_channel_koids(buffer_collection_token_request, &server_koid, &client_koid);
  if (status != ZX_OK) {
    Fail("get_channel_koids() failed - status: %d", status);
    return;
  }
  token_ptr->SetServerKoid(server_koid);

  LogInfo("CreateBufferCollectionToken() - server_koid: %lu", token_ptr->server_koid());
  token_ptr->Bind(std::move(buffer_collection_token_request));
}

void LogicalBufferCollection::OnSetConstraints() {
  MaybeAllocate();
  return;
}

LogicalBufferCollection::AllocationResult LogicalBufferCollection::allocation_result() {
  ZX_DEBUG_ASSERT(has_allocation_result_ ||
                  (allocation_result_status_ == ZX_OK && !allocation_result_info_));
  // If this assert fails, it mean we've already done ::Fail().  This should be impossible since
  // Fail() clears all BufferCollection views so they shouldn't be able to call
  // ::allocation_result().
  ZX_DEBUG_ASSERT(
      !(has_allocation_result_ && allocation_result_status_ == ZX_OK && !allocation_result_info_));
  return {
      .buffer_collection_info = allocation_result_info_.get(),
      .status = allocation_result_status_,
  };
}

LogicalBufferCollection::LogicalBufferCollection(Device* parent_device)
    : parent_device_(parent_device), constraints_(Constraints::Null) {
  LogInfo("LogicalBufferCollection::LogicalBufferCollection()");
  // nothing else to do here
}

LogicalBufferCollection::~LogicalBufferCollection() {
  LogInfo("~LogicalBufferCollection");
  // Every entry in these collections keeps a
  // fbl::RefPtr<LogicalBufferCollection>, so these should both already be
  // empty.
  ZX_DEBUG_ASSERT(token_views_.empty());
  ZX_DEBUG_ASSERT(collection_views_.empty());

  if (memory_allocator_) {
    memory_allocator_->RemoveDestroyCallback(reinterpret_cast<intptr_t>(this));
  }
}

void LogicalBufferCollection::Fail(const char* format, ...) {
  if (format) {
    va_list args;
    va_start(args, format);
    vLog(true, "LogicalBufferCollection", "fail", format, args);
    va_end(args);
  }

  // Close all the associated channels.  We do this by swapping into local
  // collections and clearing those, since deleting the items in the
  // collections will delete |this|.
  TokenMap local_token_views;
  token_views_.swap(local_token_views);
  CollectionMap local_collection_views;
  collection_views_.swap(local_collection_views);

  // Since all the token views and collection views will shortly be gone, there
  // will be no way for any client to be sent the VMOs again, so we can close
  // the handles to the VMOs here.  This is necessary in order to get
  // ZX_VMO_ZERO_CHILDREN to happen in TrackedParentVmo, but not sufficient
  // alone (clients must also close their VMO(s)).
  allocation_result_info_.reset(nullptr);

  // |this| can be deleted during these calls to clear(), unless parent_vmos_
  // isn't empty yet, or unless the caller of Fail() has its own temporary
  // fbl::RefPtr<LogicalBufferCollection> on the stack.
  //
  // These clear() calls will close the channels, which in turn will inform
  // the participants to close their child VMO handles.  We don't revoke the
  // child VMOs, so the LogicalBufferCollection will stick around until
  // parent_vmo_map_ becomes empty thanks to participants closing their child
  // VMOs.
  local_token_views.clear();
  local_collection_views.clear();
}

void LogicalBufferCollection::LogInfo(const char* format, ...) {
  va_list args;
  va_start(args, format);
  vLog(false, "LogicalBufferCollection", "info", format, args);
  va_end(args);
}

void LogicalBufferCollection::LogError(const char* format, ...) {
  va_list args;
  va_start(args, format);
  vLog(true, "LogicalBufferCollection", "error", format, args);
  va_end(args);
}

void LogicalBufferCollection::MaybeAllocate() {
  if (!token_views_.empty()) {
    // All tokens must be converted into BufferCollection views or Close()ed
    // before allocation will happen.
    return;
  }
  if (collection_views_.empty()) {
    // The LogicalBufferCollection should be failed because there are no clients left, despite only
    // getting here if all of the clients did a clean Close().
    if (is_allocate_attempted_) {
      // Only log as info because this is a normal way to destroy the buffer collection.
      LogInfo("All clients called Close(), but now zero clients remain (after allocation).");
      Fail(nullptr);
    } else {
      Fail("All clients called Close(), but now zero clients remain (before allocation).");
    }
    return;
  }
  if (is_allocate_attempted_) {
    // Allocate was already attempted.
    return;
  }
  // Sweep looking for any views that haven't set constraints.
  for (auto& [key, value] : collection_views_) {
    if (!key->is_set_constraints_seen()) {
      return;
    }
  }
  // All the views have seen SetConstraints(), and there are no tokens left.
  // Regardless of whether allocation succeeds or fails, we remember we've
  // started an attempt to allocate so we don't attempt again.
  is_allocate_attempted_ = true;
  TryAllocate();
  return;
}

// This only runs on a clean stack.
void LogicalBufferCollection::TryAllocate() {
  // If we're here it means we still have collection_views_, because if the
  // last collection view disappeared we would have run ~this which would have
  // cleared the Post() canary so this method woudn't be running.
  ZX_DEBUG_ASSERT(!collection_views_.empty());

  // Currently only BufferCollection(s) that have already done a clean Close()
  // have their constraints in constraints_list_.  Now we want all the rest of
  // the constraints represented in collection_views_ to be in
  // constraints_list_ so we can just process constraints_list_ in
  // CombineConstraints().  These can't be moved, only cloned, because the
  // still-alive BufferCollection(s) will still want to refer to their
  // constraints at least for GetUsageBasedRightsAttenuation() purposes.
  for (auto& [key, value] : collection_views_) {
    ZX_DEBUG_ASSERT(key->is_set_constraints_seen());
    if (key->constraints()) {
      constraints_list_.emplace_back(BufferCollectionConstraintsClone(key->constraints()));
    }
  }

  if (!CombineConstraints()) {
    // It's impossible to combine the constraints due to incompatible
    // constraints, or all participants set null constraints.
    SetFailedAllocationResult(ZX_ERR_NOT_SUPPORTED);
    return;
  }
  ZX_DEBUG_ASSERT(!!constraints_);

  zx_status_t allocate_result = ZX_OK;
  BufferCollectionInfo allocation = Allocate(&allocate_result);
  if (!allocation) {
    ZX_DEBUG_ASSERT(allocate_result != ZX_OK);
    SetFailedAllocationResult(allocate_result);
    return;
  }
  ZX_DEBUG_ASSERT(allocate_result == ZX_OK);

  SetAllocationResult(std::move(allocation));
  return;
}

void LogicalBufferCollection::SetFailedAllocationResult(zx_status_t status) {
  ZX_DEBUG_ASSERT(status != ZX_OK);

  // Only set result once.
  ZX_DEBUG_ASSERT(!has_allocation_result_);
  // allocation_result_status_ is initialized to ZX_OK, so should still be set
  // that way.
  ZX_DEBUG_ASSERT(allocation_result_status_ == ZX_OK);

  allocation_result_status_ = status;
  // Was initialized to nullptr.
  ZX_DEBUG_ASSERT(!allocation_result_info_);
  has_allocation_result_ = true;
  SendAllocationResult();
  return;
}

void LogicalBufferCollection::SetAllocationResult(BufferCollectionInfo info) {
  // Setting null constraints as the success case isn't allowed.  That's
  // considered a failure.  At least one participant must specify non-null
  // constraints.
  ZX_DEBUG_ASSERT(info);

  // Only set result once.
  ZX_DEBUG_ASSERT(!has_allocation_result_);
  // allocation_result_status_ is initialized to ZX_OK, so should still be set
  // that way.
  ZX_DEBUG_ASSERT(allocation_result_status_ == ZX_OK);

  allocation_result_status_ = ZX_OK;
  allocation_result_info_ = std::move(info);
  has_allocation_result_ = true;
  SendAllocationResult();
  return;
}

void LogicalBufferCollection::SendAllocationResult() {
  ZX_DEBUG_ASSERT(has_allocation_result_);
  ZX_DEBUG_ASSERT(token_views_.empty());
  ZX_DEBUG_ASSERT(!collection_views_.empty());

  for (auto& [key, value] : collection_views_) {
    // May as well assert since we can.
    ZX_DEBUG_ASSERT(key->is_set_constraints_seen());
    key->OnBuffersAllocated();
  }

  if (allocation_result_status_ != ZX_OK) {
    Fail(
        "LogicalBufferCollection::SendAllocationResult() done sending "
        "allocation failure - now auto-failing self.");
    return;
  }
}

void LogicalBufferCollection::BindSharedCollectionInternal(BufferCollectionToken* token,
                                                           zx::channel buffer_collection_request) {
  auto self = token->parent_shared();
  ZX_DEBUG_ASSERT(self.get() == this);
  auto collection = BufferCollection::Create(self);
  collection->SetErrorHandler([this, collection_ptr = collection.get()](zx_status_t status) {
    // status passed to an error handler is never ZX_OK.  Clean close is
    // ZX_ERR_PEER_CLOSED.
    ZX_DEBUG_ASSERT(status != ZX_OK);

    // We know collection_ptr is still alive because collection_ptr is
    // still in collection_views_.  We know this is still alive because
    // this has a RefPtr<> ref from collection_ptr.
    //
    // TODO(dustingreen): Switch to contains() when C++20.
    ZX_DEBUG_ASSERT(collection_views_.find(collection_ptr) != collection_views_.end());

    // The BufferCollection may have had Close() called on it, in which
    // case closure of the BufferCollection doesn't cause
    // LogicalBufferCollection failure.  Or, Close() wasn't called and
    // the LogicalBufferCollection is out of here.

    if (!(status == ZX_ERR_PEER_CLOSED && collection_ptr->is_done())) {
      // We don't have to explicitly remove collection from collection_views_ because Fail() will
      // collection_views_.clear().
      //
      // A BufferCollection view whose error handler runs implies LogicalBufferCollection failure.
      //
      // A LogicalBufferCollection intentionally treats any error that might be triggered by a
      // client failure as a LogicalBufferCollection failure, because a LogicalBufferCollection can
      // use a lot of RAM and can tend to block creating a replacement LogicalBufferCollection.
      //
      // If a participant is cleanly told to be done with a BufferCollection, the participant can
      // send Close() before BufferCollection channel close to avoid triggering this failure, in
      // case the initiator might want to continue using the BufferCollection without the
      // participant.
      //
      // TODO(ZX-3884): Provide a way to mark a BufferCollection view as expendable without implying
      // that the channel is closing, so that the client can still detect when the BufferCollection
      // VMOs need to be closed base don BufferCollection channel closure by sysmem.
      //
      // In rare cases, an initiator might choose to use Close() to avoid this failure, but more
      // typically initiators will just close their BufferCollection view without Close() first, and
      // this failure results.  This is considered acceptable partly because it helps exercise code
      // in participants that may see BufferCollection channel closure before closure of related
      // channels, and it helps get the VMO handles closed ASAP to avoid letting those continue to
      // use space of a MemoryAllocator's pool of pre-reserved space (for example).
      //
      // TODO(dustingreen): Consider providing a way for the initiator to close its channel (first)
      // such that the failure is silent, without silencing this failure on unclean close or
      // participant close, and without letting participants pretend to be the initiator re.
      // this failure's silence / non-silence.
      Fail(
          "BufferCollection (view) channel failure or closure causing LogicalBufferCollection "
          "failure - status: %d",
          status);
      return;
    }

    // At this point we know the collection_ptr is cleanly done (Close()
    // was sent from client) and can be removed from the set of tracked
    // collections.  We keep the collection's constraints (if any), as
    // those are still relevant - this lets a participant do
    // SetConstraints() followed by Close() followed by closing the
    // participant's BufferCollection channel, which is convenient for
    // some participants.
    //
    // If this causes collection_tokens_.empty() and collection_views_.empty(),
    // MaybeAllocate() takes care of calling Fail().

    if (collection_ptr->is_set_constraints_seen()) {
      constraints_list_.emplace_back(collection_ptr->TakeConstraints());
    }

    auto self = collection_ptr->parent_shared();
    ZX_DEBUG_ASSERT(self.get() == this);
    collection_views_.erase(collection_ptr);
    MaybeAllocate();
    // ~self may delete "this"
    return;
  });
  auto collection_ptr = collection.get();
  collection_views_.insert({collection_ptr, std::move(collection)});
  // ~BufferCollectionToken calls UntrackTokenKoid().
  token_views_.erase(token);
  collection_ptr->Bind(std::move(buffer_collection_request));
}

bool LogicalBufferCollection::CombineConstraints() {
  // This doesn't necessarily mean that any of the collection_views_ have
  // set non-null constraints though.  We do require that at least one
  // participant (probably the initiator) retains an open channel to its
  // BufferCollection until allocation is done, else allocation won't be
  // attempted.
  ZX_DEBUG_ASSERT(!collection_views_.empty());

  // We also know that all the constraints are in constraints_list_ now,
  // including all constraints from collection_views_.
  ZX_DEBUG_ASSERT(!constraints_list_.empty());

  auto iter = std::find_if(constraints_list_.begin(), constraints_list_.end(),
                           [](auto& item) { return !!item; });
  if (iter == constraints_list_.end()) {
    // This is a failure.  At least one participant must provide
    // constraints.
    return false;
  }

  if (!CheckSanitizeBufferCollectionConstraints(iter->get(), false)) {
    return false;
  }

  Constraints result = BufferCollectionConstraintsClone(iter->get());
  ++iter;

  for (; iter != constraints_list_.end(); ++iter) {
    if (!iter->get()) {
      continue;
    }
    if (!CheckSanitizeBufferCollectionConstraints(iter->get(), false)) {
      return false;
    }
    if (!AccumulateConstraintBufferCollection(result.get(), iter->get())) {
      // This is a failure.  The space of permitted settings contains no
      // points.
      return false;
    }
  }

  if (!CheckSanitizeBufferCollectionConstraints(result.get(), true)) {
    return false;
  }

  constraints_ = std::move(result);
  return true;
}

// Nearly all constraint checks must go under here or under ::Allocate() (not in
// the Accumulate* methods), else we could fail to notice a single participant
// providing unsatisfiable constraints, where no Accumulate* happens.  The
// constraint checks that are present under Accumulate* are commented explaining
// why it's ok for them to be there.
bool LogicalBufferCollection::CheckSanitizeBufferCollectionConstraints(
    fuchsia_sysmem_BufferCollectionConstraints* constraints, bool is_aggregated) {
  FieldDefaultMax(&constraints->max_buffer_count);
  if (constraints->min_buffer_count > constraints->max_buffer_count) {
    LogError("min_buffer_count > max_buffer_count");
    return false;
  }
  if (!is_aggregated) {
    // At least one usage bit must be specified by any participant that
    // specifies constraints.  The "none" usage bit can be set by a participant
    // that doesn't directly use the buffers, so we know that the participant
    // didn't forget to set usage.
    if (constraints->usage.none == 0 && constraints->usage.cpu == 0 &&
        constraints->usage.vulkan == 0 && constraints->usage.display == 0 &&
        constraints->usage.video == 0) {
      LogError("At least one usage bit must be set by a participant.");
      return false;
    }
    if (constraints->usage.none != 0) {
      if (constraints->usage.cpu != 0 || constraints->usage.vulkan != 0 ||
          constraints->usage.display != 0 || constraints->usage.video != 0) {
        LogError("A participant indicating 'none' usage can't specify any other usage.");
        return false;
      }
    }
  } else {
    if (constraints->usage.cpu == 0 && constraints->usage.vulkan == 0 &&
        constraints->usage.display == 0 && constraints->usage.video == 0) {
      LogError("At least one non-'none' usage bit must be set across all participants.");
      return false;
    }
  }
  if (!constraints->has_buffer_memory_constraints) {
    // The CheckSanitizeBufferMemoryConstraints() further down will help fill out
    // the "max" fields, but !has_buffer_memory_constraints implies particular
    // defaults for some bool fields, so fill those out here.
    constraints->buffer_memory_constraints = fuchsia_sysmem_BufferMemoryConstraints{};
    // The CPU domain is supported by default.
    constraints->buffer_memory_constraints.cpu_domain_supported = true;
    // If !usage.cpu, then participant doesn't care what domain, so indicate support
    // for RAM and inaccessible domains in that case.
    constraints->buffer_memory_constraints.ram_domain_supported = !constraints->usage.cpu;
    constraints->buffer_memory_constraints.inaccessible_domain_supported = !constraints->usage.cpu;
    constraints->has_buffer_memory_constraints = true;
  }
  ZX_DEBUG_ASSERT(constraints->has_buffer_memory_constraints);
  if (IsCpuUsage(constraints->usage) &&
      constraints->buffer_memory_constraints.inaccessible_domain_supported) {
    LogError("IsCpuUsage && inaccessible_domain_supported doesn't make sense.");
    return false;
  }
  if (!CheckSanitizeBufferMemoryConstraints(&constraints->buffer_memory_constraints)) {
    return false;
  }
  for (uint32_t i = 0; i < constraints->image_format_constraints_count; ++i) {
    if (!CheckSanitizeImageFormatConstraints(&constraints->image_format_constraints[i])) {
      return false;
    }
  }
  return true;
}

static bool IsHeapPermitted(const fuchsia_sysmem_BufferMemoryConstraints* constraints,
                            fuchsia_sysmem_HeapType heap) {
  if (constraints->heap_permitted_count) {
    auto begin = constraints->heap_permitted;
    auto end = constraints->heap_permitted + constraints->heap_permitted_count;
    return std::find(begin, end, heap) != end;
  }
  return true;
}

bool LogicalBufferCollection::CheckSanitizeBufferMemoryConstraints(
    fuchsia_sysmem_BufferMemoryConstraints* constraints) {
  FieldDefaultZero(&constraints->min_size_bytes);
  FieldDefaultMax(&constraints->max_size_bytes);

  if (constraints->min_size_bytes > constraints->max_size_bytes) {
    LogError("min_size_bytes > max_size_bytes");
    return false;
  }
  bool secure_permitted = IsHeapPermitted(constraints, fuchsia_sysmem_HeapType_AMLOGIC_SECURE);
  if (constraints->secure_required && !secure_permitted) {
    LogError("secure memory required but not permitted");
    return false;
  }
  return true;
}

bool LogicalBufferCollection::CheckSanitizeImageFormatConstraints(
    fuchsia_sysmem_ImageFormatConstraints* constraints) {
  FieldDefault1(&constraints->coded_width_divisor);
  FieldDefault1(&constraints->coded_height_divisor);
  FieldDefault1(&constraints->bytes_per_row_divisor);
  FieldDefault1(&constraints->start_offset_divisor);
  FieldDefault1(&constraints->display_width_divisor);
  FieldDefault1(&constraints->display_height_divisor);

  FieldDefaultMax(&constraints->required_min_coded_width);
  FieldDefaultZero(&constraints->required_max_coded_width);
  FieldDefaultMax(&constraints->required_min_coded_height);
  FieldDefaultZero(&constraints->required_max_coded_height);
  FieldDefaultMax(&constraints->required_min_bytes_per_row);
  FieldDefaultZero(&constraints->required_max_bytes_per_row);

  uint32_t min_bytes_per_row_given_min_width =
      ImageFormatStrideBytesPerWidthPixel(&constraints->pixel_format) *
      constraints->min_coded_width;
  constraints->min_bytes_per_row =
      std::max(constraints->min_bytes_per_row, min_bytes_per_row_given_min_width);

  if (constraints->pixel_format.type == fuchsia_sysmem_PixelFormatType_INVALID) {
    LogError("PixelFormatType INVALID not allowed");
    return false;
  }
  if (!ImageFormatIsSupported(&constraints->pixel_format)) {
    LogError("Unsupported pixel format");
    return false;
  }

  if (!constraints->color_spaces_count) {
    LogError("color_spaces_count == 0 not allowed");
    return false;
  }
  if (constraints->layers != 1) {
    LogError("layers != 1 is not yet implemented");
    return false;
  }

  if (constraints->min_coded_width > constraints->max_coded_width) {
    LogError("min_coded_width > max_coded_width");
    return false;
  }
  if (constraints->min_coded_height > constraints->max_coded_height) {
    LogError("min_coded_height > max_coded_height");
    return false;
  }
  if (constraints->min_bytes_per_row > constraints->max_bytes_per_row) {
    LogError("min_bytes_per_row > max_bytes_per_row");
    return false;
  }
  if (constraints->min_coded_width * constraints->min_coded_height >
      constraints->max_coded_width_times_coded_height) {
    LogError(
        "min_coded_width * min_coded_height > "
        "max_coded_width_times_coded_height");
    return false;
  }
  if (constraints->layers != 1) {
    LogError("layers != 1 is not yet implemented");
    return false;
  }

  if (!IsNonZeroPowerOf2(constraints->coded_width_divisor)) {
    LogError("non-power-of-2 coded_width_divisor not supported");
    return false;
  }
  if (!IsNonZeroPowerOf2(constraints->coded_height_divisor)) {
    LogError("non-power-of-2 coded_width_divisor not supported");
    return false;
  }
  if (!IsNonZeroPowerOf2(constraints->bytes_per_row_divisor)) {
    LogError("non-power-of-2 bytes_per_row_divisor not supported");
    return false;
  }
  if (!IsNonZeroPowerOf2(constraints->start_offset_divisor)) {
    LogError("non-power-of-2 start_offset_divisor not supported");
    return false;
  }
  if (constraints->start_offset_divisor > PAGE_SIZE) {
    LogError("support for start_offset_divisor > PAGE_SIZE not yet implemented");
    return false;
  }
  if (!IsNonZeroPowerOf2(constraints->display_width_divisor)) {
    LogError("non-power-of-2 display_width_divisor not supported");
    return false;
  }
  if (!IsNonZeroPowerOf2(constraints->display_height_divisor)) {
    LogError("non-power-of-2 display_height_divisor not supported");
    return false;
  }

  for (uint32_t i = 0; i < constraints->color_spaces_count; ++i) {
    if (!ImageFormatIsSupportedColorSpaceForPixelFormat(constraints->color_space[i],
                                                        constraints->pixel_format)) {
      LogError(
          "!ImageFormatIsSupportedColorSpaceForPixelFormat() "
          "color_space.type: %u "
          "pixel_format.type: %u",
          constraints->color_space[i].type, constraints->pixel_format.type);
      return false;
    }
  }

  ZX_DEBUG_ASSERT(constraints->required_min_coded_width != 0);
  if (constraints->required_min_coded_width < constraints->min_coded_width) {
    LogError("required_min_coded_width < min_coded_width");
    return false;
  }
  if (constraints->required_max_coded_width > constraints->max_coded_width) {
    LogError("required_max_coded_width > max_coded_width");
    return false;
  }
  ZX_DEBUG_ASSERT(constraints->required_min_coded_height != 0);
  if (constraints->required_min_coded_height < constraints->min_coded_height) {
    LogError("required_min_coded_height < min_coded_height");
    return false;
  }
  if (constraints->required_max_coded_height > constraints->max_coded_height) {
    LogError("required_max_coded_height > max_coded_height");
    return false;
  }
  ZX_DEBUG_ASSERT(constraints->required_min_bytes_per_row != 0);
  if (constraints->required_min_bytes_per_row < constraints->min_bytes_per_row) {
    LogError("required_min_bytes_per_row < min_bytes_per_row");
    return false;
  }
  if (constraints->required_max_bytes_per_row > constraints->max_bytes_per_row) {
    LogError("required_max_bytes_per_row > max_bytes_per_row");
    return false;
  }

  // TODO(dustingreen): Check compatibility of color_space[] entries vs. the
  // pixel_format.  In particular, 2020 and 2100 don't have 8 bpp, only 10 or
  // 12 bpp, while a given PixelFormat.type is a specific bpp.  There's
  // probably no reason to allow 2020 or 2100 to be specified along with a
  // PixelFormat.type that's 8 bpp for example.

  return true;
}

LogicalBufferCollection::Constraints LogicalBufferCollection::BufferCollectionConstraintsClone(
    const fuchsia_sysmem_BufferCollectionConstraints* input) {
  // There are no handles in BufferCollectionConstraints, so just copy the
  // payload.  If any handles are added later we'll have to fix this up.
  return Constraints(*input);
}

LogicalBufferCollection::ImageFormatConstraints
LogicalBufferCollection::ImageFormatConstraintsClone(
    const fuchsia_sysmem_ImageFormatConstraints* input) {
  // There are no handles in ImageFormatConstraints, so just copy the
  // payload.  If any handles are added later we'll have to fix this up.
  return ImageFormatConstraints(*input);
}

// |acc| accumulated constraints so far
//
// |c| additional constraint to aggregate into acc
bool LogicalBufferCollection::AccumulateConstraintBufferCollection(
    fuchsia_sysmem_BufferCollectionConstraints* acc,
    const fuchsia_sysmem_BufferCollectionConstraints* c) {
  // We accumulate "none" usage just like other usages, to make aggregation
  // and CheckSanitize consistent/uniform.
  acc->usage.none |= c->usage.none;
  acc->usage.cpu |= c->usage.cpu;
  acc->usage.vulkan |= c->usage.vulkan;
  acc->usage.display |= c->usage.display;
  acc->usage.video |= c->usage.video;

  acc->min_buffer_count_for_camping += c->min_buffer_count_for_camping;
  acc->min_buffer_count_for_dedicated_slack += c->min_buffer_count_for_dedicated_slack;
  acc->min_buffer_count_for_shared_slack =
      std::max(acc->min_buffer_count_for_shared_slack, c->min_buffer_count_for_shared_slack);

  acc->min_buffer_count = std::max(acc->min_buffer_count, c->min_buffer_count);
  // 0 is replaced with 0xFFFFFFFF in
  // CheckSanitizeBufferCollectionConstraints.
  ZX_DEBUG_ASSERT(acc->max_buffer_count != 0);
  ZX_DEBUG_ASSERT(c->max_buffer_count != 0);
  acc->max_buffer_count = std::min(acc->max_buffer_count, c->max_buffer_count);

  // CheckSanitizeBufferCollectionConstraints() takes care of setting a default
  // buffer_collection_constraints, so we can assert that both acc and c "has_" one.
  ZX_DEBUG_ASSERT(acc->has_buffer_memory_constraints);
  ZX_DEBUG_ASSERT(c->has_buffer_memory_constraints);
  if (!AccumulateConstraintBufferMemory(&acc->buffer_memory_constraints,
                                        &c->buffer_memory_constraints)) {
    return false;
  }

  // Reject secure_required in combination with any CPU usage, since CPU usage
  // isn't possible given secure memory.
  if (acc->buffer_memory_constraints.secure_required && IsCpuUsage(acc->usage)) {
    return false;
  }

  if (!acc->image_format_constraints_count) {
    for (uint32_t i = 0; i < c->image_format_constraints_count; ++i) {
      // struct copy
      acc->image_format_constraints[i] = c->image_format_constraints[i];
    }
    acc->image_format_constraints_count = c->image_format_constraints_count;
  } else {
    ZX_DEBUG_ASSERT(acc->image_format_constraints_count);
    if (c->image_format_constraints_count) {
      if (!AccumulateConstraintImageFormats(
              &acc->image_format_constraints_count, acc->image_format_constraints,
              c->image_format_constraints_count, c->image_format_constraints)) {
        // We return false if we've seen non-zero
        // image_format_constraint_count from at least one participant
        // but among non-zero image_format_constraint_count participants
        // since then the overlap has dropped to empty set.
        //
        // This path is taken when there are completely non-overlapping
        // PixelFormats and also when PixelFormat(s) overlap but none
        // of those have any non-empty settings space remaining.  In
        // that case we've removed the PixelFormat from consideration
        // despite it being common among participants (so far).
        return false;
      }
      ZX_DEBUG_ASSERT(acc->image_format_constraints_count);
    }
  }

  // acc->image_format_constraints_count == 0 is allowed here, when all
  // participants had image_format_constraints_count == 0.
  return true;
}

bool LogicalBufferCollection::AccumulateConstraintHeapPermitted(uint32_t* acc_count,
                                                                fuchsia_sysmem_HeapType acc[],
                                                                uint32_t c_count,
                                                                const fuchsia_sysmem_HeapType c[]) {
  // Remove any heap in acc that's not in c.  If zero heaps
  // remain in acc, return false.
  ZX_DEBUG_ASSERT(*acc_count > 0);

  for (uint32_t ai = 0; ai < *acc_count; ++ai) {
    uint32_t ci;
    for (ci = 0; ci < c_count; ++ci) {
      if (acc[ai] == c[ci]) {
        // We found heap in c.  Break so we can move on to
        // the next heap.
        break;
      }
    }
    if (ci == c_count) {
      // remove from acc because not found in c
      --(*acc_count);
      // copy of formerly last item on top of the item being
      // removed
      acc[ai] = acc[*acc_count];
      // adjust ai to force current index to be processed again as it's
      // now a different item
      --ai;
    }
  }

  if (!*acc_count) {
    LogError("Zero heap permitted overlap");
    return false;
  }

  return true;
}

bool LogicalBufferCollection::AccumulateConstraintBufferMemory(
    fuchsia_sysmem_BufferMemoryConstraints* acc, const fuchsia_sysmem_BufferMemoryConstraints* c) {
  acc->min_size_bytes = std::max(acc->min_size_bytes, c->min_size_bytes);

  // Don't permit 0 as the overall min_size_bytes; that would be nonsense.  No
  // particular initiator should feel that it has to specify 1 in this field;
  // that's just built into sysmem instead.  While a VMO will have a minimum
  // actual size of page size, we do permit treating buffers as if they're 1
  // byte, mainly for testing reasons, and to avoid any unnecessary dependence
  // or assumptions re. page size.
  acc->min_size_bytes = std::max(acc->min_size_bytes, 1u);
  acc->max_size_bytes = std::min(acc->max_size_bytes, c->max_size_bytes);

  acc->physically_contiguous_required =
      acc->physically_contiguous_required || c->physically_contiguous_required;

  acc->secure_required = acc->secure_required || c->secure_required;

  acc->ram_domain_supported = acc->ram_domain_supported && c->ram_domain_supported;
  acc->cpu_domain_supported = acc->cpu_domain_supported && c->cpu_domain_supported;
  acc->inaccessible_domain_supported =
      acc->inaccessible_domain_supported && c->inaccessible_domain_supported;

  if (!acc->heap_permitted_count) {
    std::copy(c->heap_permitted, c->heap_permitted + c->heap_permitted_count, acc->heap_permitted);
    acc->heap_permitted_count = c->heap_permitted_count;
  } else {
    if (c->heap_permitted_count) {
      if (!AccumulateConstraintHeapPermitted(&acc->heap_permitted_count, acc->heap_permitted,
                                             c->heap_permitted_count, c->heap_permitted)) {
        return false;
      }
    }
  }
  return true;
}

bool LogicalBufferCollection::AccumulateConstraintImageFormats(
    uint32_t* acc_count, fuchsia_sysmem_ImageFormatConstraints acc[], uint32_t c_count,
    const fuchsia_sysmem_ImageFormatConstraints c[]) {
  // Remove any pixel_format in acc that's not in c.  Process any format
  // that's in both.  If processing the format results in empty set for that
  // format, pretend as if the format wasn't in c and remove that format from
  // acc.  If acc ends up with zero formats, return false.

  // This method doesn't get called unless there's at least one format in
  // acc.
  ZX_DEBUG_ASSERT(*acc_count);

  for (uint32_t ai = 0; ai < *acc_count; ++ai) {
    uint32_t ci;
    for (ci = 0; ci < c_count; ++ci) {
      if (ImageFormatIsPixelFormatEqual(acc[ai].pixel_format, c[ci].pixel_format)) {
        if (!AccumulateConstraintImageFormat(&acc[ai], &c[ci])) {
          // Pretend like the format wasn't in c to begin with, so
          // this format gets removed from acc.  Only if this results
          // in zero formats in acc do we end up returning false.
          ci = c_count;
          break;
        }
        // We found the format in c and processed the format without
        // that resulting in empty set; break so we can move on to the
        // next format.
        break;
      }
    }
    if (ci == c_count) {
      // remove from acc because not found in c
      --(*acc_count);
      // struct copy of formerly last item on top of the item being
      // removed
      acc[ai] = acc[*acc_count];
      // adjust ai to force current index to be processed again as it's
      // now a different item
      --ai;
    }
  }

  if (!*acc_count) {
    // It's ok for this check to be under Accumulate* because it's permitted
    // for a given participant to have zero image_format_constraints_count.
    // It's only when the count becomes non-zero then drops back to zero
    // (checked here), or if we end up with no image format constraints and
    // no buffer constraints (checked in ::Allocate()), that we care.
    LogError("all pixel_format(s) eliminated");
    return false;
  }

  return true;
}

bool LogicalBufferCollection::AccumulateConstraintImageFormat(
    fuchsia_sysmem_ImageFormatConstraints* acc, const fuchsia_sysmem_ImageFormatConstraints* c) {
  ZX_DEBUG_ASSERT(ImageFormatIsPixelFormatEqual(acc->pixel_format, c->pixel_format));
  // Checked previously.
  ZX_DEBUG_ASSERT(acc->color_spaces_count);
  // Checked previously.
  ZX_DEBUG_ASSERT(c->color_spaces_count);

  if (!AccumulateConstraintColorSpaces(&acc->color_spaces_count, acc->color_space,
                                       c->color_spaces_count, c->color_space)) {
    return false;
  }
  // Else AccumulateConstraintColorSpaces() would have returned false.
  ZX_DEBUG_ASSERT(acc->color_spaces_count);

  acc->min_coded_width = std::max(acc->min_coded_width, c->min_coded_width);
  acc->max_coded_width = std::min(acc->max_coded_width, c->max_coded_width);
  acc->min_coded_height = std::max(acc->min_coded_height, c->min_coded_height);
  acc->max_coded_height = std::min(acc->max_coded_height, c->max_coded_height);
  acc->min_bytes_per_row = std::max(acc->min_bytes_per_row, c->min_bytes_per_row);
  acc->max_bytes_per_row = std::min(acc->max_bytes_per_row, c->max_bytes_per_row);
  acc->max_coded_width_times_coded_height =
      std::min(acc->max_coded_width_times_coded_height, c->max_coded_width_times_coded_height);

  // Checked previously.
  ZX_DEBUG_ASSERT(acc->layers == 1);

  acc->coded_width_divisor = std::max(acc->coded_width_divisor, c->coded_width_divisor);
  acc->coded_width_divisor =
      std::max(acc->coded_width_divisor, ImageFormatCodedWidthMinDivisor(&acc->pixel_format));

  acc->coded_height_divisor = std::max(acc->coded_height_divisor, c->coded_height_divisor);
  acc->coded_height_divisor =
      std::max(acc->coded_height_divisor, ImageFormatCodedHeightMinDivisor(&acc->pixel_format));

  acc->bytes_per_row_divisor = std::max(acc->bytes_per_row_divisor, c->bytes_per_row_divisor);
  acc->bytes_per_row_divisor =
      std::max(acc->bytes_per_row_divisor, ImageFormatSampleAlignment(&acc->pixel_format));

  acc->start_offset_divisor = std::max(acc->start_offset_divisor, c->start_offset_divisor);
  acc->start_offset_divisor =
      std::max(acc->start_offset_divisor, ImageFormatSampleAlignment(&acc->pixel_format));

  acc->display_width_divisor = std::max(acc->display_width_divisor, c->display_width_divisor);
  acc->display_height_divisor = std::max(acc->display_height_divisor, c->display_height_divisor);

  // The required_ space is accumulated by taking the union, and must be fully
  // within the non-required_ space, else fail.  For example, this allows a
  // video decoder to indicate that it's capable of outputting a wide range of
  // output dimensions, but that it has specific current dimensions that are
  // presently required_ (min == max) for decode to proceed.
  ZX_DEBUG_ASSERT(acc->required_min_coded_width != 0);
  ZX_DEBUG_ASSERT(c->required_min_coded_width != 0);
  acc->required_min_coded_width =
      std::min(acc->required_min_coded_width, c->required_min_coded_width);
  acc->required_max_coded_width =
      std::max(acc->required_max_coded_width, c->required_max_coded_width);
  ZX_DEBUG_ASSERT(acc->required_min_coded_height != 0);
  ZX_DEBUG_ASSERT(c->required_min_coded_height != 0);
  acc->required_min_coded_height =
      std::min(acc->required_min_coded_height, c->required_min_coded_height);
  acc->required_max_coded_height =
      std::max(acc->required_max_coded_height, c->required_max_coded_height);
  ZX_DEBUG_ASSERT(acc->required_min_bytes_per_row != 0);
  ZX_DEBUG_ASSERT(c->required_min_bytes_per_row != 0);
  acc->required_min_bytes_per_row =
      std::min(acc->required_min_bytes_per_row, c->required_min_bytes_per_row);
  acc->required_max_bytes_per_row =
      std::max(acc->required_max_bytes_per_row, c->required_max_bytes_per_row);

  return true;
}

bool LogicalBufferCollection::AccumulateConstraintColorSpaces(uint32_t* acc_count,
                                                              fuchsia_sysmem_ColorSpace acc[],
                                                              uint32_t c_count,
                                                              const fuchsia_sysmem_ColorSpace c[]) {
  // Remove any color_space in acc that's not in c.  If zero color spaces
  // remain in acc, return false.

  for (uint32_t ai = 0; ai < *acc_count; ++ai) {
    uint32_t ci;
    for (ci = 0; ci < c_count; ++ci) {
      if (IsColorSpaceEqual(acc[ai], c[ci])) {
        // We found the color space in c.  Break so we can move on to
        // the next color space.
        break;
      }
    }
    if (ci == c_count) {
      // remove from acc because not found in c
      --(*acc_count);
      // struct copy of formerly last item on top of the item being
      // removed
      acc[ai] = acc[*acc_count];
      // adjust ai to force current index to be processed again as it's
      // now a different item
      --ai;
    }
  }

  if (!*acc_count) {
    // It's ok for this check to be under Accumulate* because it's also
    // under CheckSanitize().  It's fine to provide a slightly more helpful
    // error message here and early out here.
    LogError("Zero color_space overlap");
    return false;
  }

  return true;
}

bool LogicalBufferCollection::IsColorSpaceEqual(const fuchsia_sysmem_ColorSpace& a,
                                                const fuchsia_sysmem_ColorSpace& b) {
  return a.type == b.type;
}

static uint64_t GetHeap(const fuchsia_sysmem_BufferMemoryConstraints* constraints) {
  if (constraints->secure_required) {
    // checked previously
    ZX_DEBUG_ASSERT(!(constraints->secure_required &&
                      !IsHeapPermitted(constraints, fuchsia_sysmem_HeapType_AMLOGIC_SECURE)));
    return fuchsia_sysmem_HeapType_AMLOGIC_SECURE;
  }
  if (IsHeapPermitted(constraints, fuchsia_sysmem_HeapType_SYSTEM_RAM)) {
    return fuchsia_sysmem_HeapType_SYSTEM_RAM;
  }
  ZX_DEBUG_ASSERT(constraints->heap_permitted_count);
  return constraints->heap_permitted[0];
}

static bool GetCoherencyDomain(const fuchsia_sysmem_BufferCollectionConstraints* constraints,
                               MemoryAllocator* memory_allocator,
                               fuchsia_sysmem_CoherencyDomain* domain_out) {
  ZX_DEBUG_ASSERT(constraints->has_buffer_memory_constraints);
  // The heap not being accessible from the CPU can force Inaccessible as the only
  // potential option.
  if (memory_allocator->CoherencyDomainIsInaccessible()) {
    if (!constraints->buffer_memory_constraints.inaccessible_domain_supported) {
      return false;
    }
    *domain_out = fuchsia_sysmem_CoherencyDomain_INACCESSIBLE;
    return true;
  }

  // Display prefers RAM coherency domain for now.
  if (constraints->usage.display != 0) {
    if (constraints->buffer_memory_constraints.ram_domain_supported) {
      // Display controllers generally aren't cache coherent, so prefer
      // RAM coherency domain.
      //
      // TODO - base on the system in use.
      *domain_out = fuchsia_sysmem_CoherencyDomain_RAM;
      return true;
    }
  }

  // If none of the above cases apply, then prefer CPU, RAM, Inaccessible
  // in that order.

  if (constraints->buffer_memory_constraints.cpu_domain_supported) {
    *domain_out = fuchsia_sysmem_CoherencyDomain_CPU;
    return true;
  }

  if (constraints->buffer_memory_constraints.ram_domain_supported) {
    *domain_out = fuchsia_sysmem_CoherencyDomain_RAM;
    return true;
  }

  if (constraints->buffer_memory_constraints.inaccessible_domain_supported) {
    // Intentionally permit treating as Inaccessible if we reach here, even
    // if the heap permits CPU access.  Only domain in common among
    // participants is Inaccessible.
    *domain_out = fuchsia_sysmem_CoherencyDomain_INACCESSIBLE;
    return true;
  }

  return false;
}

BufferCollection::BufferCollectionInfo LogicalBufferCollection::Allocate(
    zx_status_t* allocation_result) {
  ZX_DEBUG_ASSERT(constraints_);
  ZX_DEBUG_ASSERT(allocation_result);

  // Unless fails later.
  *allocation_result = ZX_OK;

  BufferCollection::BufferCollectionInfo result(BufferCollection::BufferCollectionInfo::Default);

  uint32_t min_buffer_count = constraints_->min_buffer_count_for_camping +
                              constraints_->min_buffer_count_for_dedicated_slack +
                              constraints_->min_buffer_count_for_shared_slack;
  min_buffer_count = std::max(min_buffer_count, constraints_->min_buffer_count);
  uint32_t max_buffer_count = constraints_->max_buffer_count;
  if (min_buffer_count > max_buffer_count) {
    LogError(
        "aggregate min_buffer_count > aggregate max_buffer_count - "
        "min: %u max: %u",
        min_buffer_count, max_buffer_count);
    *allocation_result = ZX_ERR_NOT_SUPPORTED;
    return BufferCollection::BufferCollectionInfo(BufferCollection::BufferCollectionInfo::Null);
  }

  result->buffer_count = min_buffer_count;
  ZX_DEBUG_ASSERT(result->buffer_count <= max_buffer_count);

  uint64_t min_size_bytes = 0;
  uint64_t max_size_bytes = std::numeric_limits<uint64_t>::max();

  fuchsia_sysmem_SingleBufferSettings* settings = &result->settings;
  fuchsia_sysmem_BufferMemorySettings* buffer_settings = &settings->buffer_settings;

  // It's allowed for zero participants to have buffer_memory_constraints, as
  // long as at least one participant has image_format_constraint_count != 0.
  if (!constraints_->has_buffer_memory_constraints &&
      !constraints_->image_format_constraints_count) {
    // Too unconstrained...  We refuse to allocate buffers without any size
    // bounds from any participant.  At least one particpant must provide
    // some form of size bounds (in terms of buffer size bounds or in terms
    // of image size bounds).
    LogError(
        "at least one participant must specify "
        "buffer_memory_constraints or "
        "image_format_constraints");
    *allocation_result = ZX_ERR_NOT_SUPPORTED;
    return BufferCollection::BufferCollectionInfo(BufferCollection::BufferCollectionInfo::Null);
  }
  if (constraints_->has_buffer_memory_constraints) {
    const fuchsia_sysmem_BufferMemoryConstraints* buffer_constraints =
        &constraints_->buffer_memory_constraints;
    buffer_settings->is_physically_contiguous = buffer_constraints->physically_contiguous_required;
    // checked previously
    ZX_DEBUG_ASSERT(!(buffer_constraints->secure_required && IsCpuUsage(constraints_->usage)));
    buffer_settings->is_secure = buffer_constraints->secure_required;
    buffer_settings->heap = GetHeap(buffer_constraints);
    // We can't fill out buffer_settings yet because that also depends on
    // ImageFormatConstraints.  We do need the min and max from here though.
    min_size_bytes = buffer_constraints->min_size_bytes;
    max_size_bytes = buffer_constraints->max_size_bytes;
  }

  // Get memory allocator for settings.
  MemoryAllocator* allocator = parent_device_->GetAllocator(buffer_settings);
  if (!allocator) {
    LogError("No memory allocator for buffer settings");
    *allocation_result = ZX_ERR_NO_MEMORY;
    return BufferCollection::BufferCollectionInfo(BufferCollection::BufferCollectionInfo::Null);
  }

  if (!GetCoherencyDomain(constraints_.get(), allocator, &buffer_settings->coherency_domain)) {
    LogError("No coherency domain found for buffer constraints");
    *allocation_result = ZX_ERR_NOT_SUPPORTED;
    return BufferCollection::BufferCollectionInfo(BufferCollection::BufferCollectionInfo::Null);
  }

  ZX_DEBUG_ASSERT(constraints_->usage.cpu == 0 ||
                  buffer_settings->coherency_domain != fuchsia_sysmem_CoherencyDomain_INACCESSIBLE);

  // It's allowed for zero participants to have any ImageFormatConstraint(s),
  // in which case the combined constraints_ will have zero (and that's fine,
  // when allocating raw buffers that don't need any ImageFormatConstraint).
  //
  // At least for now, we pick which PixelFormat to use before determining if
  // the constraints associated with that PixelFormat imply a buffer size
  // range in min_size_bytes..max_size_bytes.
  if (constraints_->image_format_constraints_count) {
    // Pick the best ImageFormatConstraints.
    uint32_t best_index = 0;
    for (uint32_t i = 1; i < constraints_->image_format_constraints_count; ++i) {
      if (CompareImageFormatConstraintsByIndex(i, best_index) < 0) {
        best_index = i;
      }
    }
    // struct copy - if right hand side's clone results in any duplicated
    // handles, those will be owned by result.
    settings->image_format_constraints =
        *ImageFormatConstraintsClone(&constraints_->image_format_constraints[best_index]).get();
    settings->has_image_format_constraints = true;
  }

  // Compute the min buffer size implied by image_format_constraints, so we
  // ensure the buffers can hold the min-size image.
  if (settings->has_image_format_constraints) {
    const fuchsia_sysmem_ImageFormatConstraints* constraints = &settings->image_format_constraints;
    fuchsia_sysmem_ImageFormat_2 min_image{};

    // struct copy
    min_image.pixel_format = constraints->pixel_format;

    // We use required_max_coded_width because that's the max width that the producer (or
    // initiator) wants these buffers to be able to hold.
    min_image.coded_width =
        AlignUp(std::max(constraints->min_coded_width, constraints->required_max_coded_width),
                constraints->coded_width_divisor);
    if (min_image.coded_width > constraints->max_coded_width) {
      LogError("coded_width_divisor caused coded_width > max_coded_width");
      *allocation_result = ZX_ERR_NOT_SUPPORTED;
      return BufferCollection::BufferCollectionInfo(BufferCollection::BufferCollectionInfo::Null);
    }
    // We use required_max_coded_height because that's the max height that the producer (or
    // initiator) wants these buffers to be able to hold.
    min_image.coded_height =
        AlignUp(std::max(constraints->min_coded_height, constraints->required_max_coded_height),
                constraints->coded_height_divisor);
    if (min_image.coded_height > constraints->max_coded_height) {
      LogError("coded_height_divisor caused coded_height > max_coded_height");
      *allocation_result = ZX_ERR_NOT_SUPPORTED;
      return BufferCollection::BufferCollectionInfo(BufferCollection::BufferCollectionInfo::Null);
    }
    min_image.bytes_per_row =
        AlignUp(std::max(constraints->min_bytes_per_row,
                         ImageFormatStrideBytesPerWidthPixel(&constraints->pixel_format) *
                             min_image.coded_width),
                constraints->bytes_per_row_divisor);
    if (min_image.bytes_per_row > constraints->max_bytes_per_row) {
      LogError(
          "bytes_per_row_divisor caused bytes_per_row > "
          "max_bytes_per_row");
      *allocation_result = ZX_ERR_NOT_SUPPORTED;
      return BufferCollection::BufferCollectionInfo(BufferCollection::BufferCollectionInfo::Null);
    }

    if (min_image.coded_width * min_image.coded_height >
        constraints->max_coded_width_times_coded_height) {
      LogError(
          "coded_width * coded_height > "
          "max_coded_width_times_coded_height");
      *allocation_result = ZX_ERR_NOT_SUPPORTED;
      return BufferCollection::BufferCollectionInfo(BufferCollection::BufferCollectionInfo::Null);
    }

    // These don't matter for computing size in bytes.
    ZX_DEBUG_ASSERT(min_image.display_width == 0);
    ZX_DEBUG_ASSERT(min_image.display_height == 0);

    // This is the only supported value for layers for now.
    min_image.layers = 1;

    // Checked previously.
    ZX_DEBUG_ASSERT(constraints->color_spaces_count >= 1);
    // This doesn't matter for computing size in bytes, as we trust the
    // pixel_format to fully specify the image size.  But set it to the
    // first ColorSpace anyway, just so the color_space.type is a valid
    // value.
    //
    // struct copy
    min_image.color_space = constraints->color_space[0];

    uint64_t image_min_size_bytes = ImageFormatImageSize(&min_image);

    if (image_min_size_bytes > min_size_bytes) {
      if (image_min_size_bytes > max_size_bytes) {
        LogError("image_min_size_bytes > max_size_bytes");
        *allocation_result = ZX_ERR_NOT_SUPPORTED;
        return BufferCollection::BufferCollectionInfo(BufferCollection::BufferCollectionInfo::Null);
      }
      min_size_bytes = image_min_size_bytes;
      ZX_DEBUG_ASSERT(min_size_bytes <= max_size_bytes);
    }
  }

  if (min_size_bytes == 0) {
    LogError("min_size_bytes == 0");
    *allocation_result = ZX_ERR_NOT_SUPPORTED;
    return BufferCollection::BufferCollectionInfo(BufferCollection::BufferCollectionInfo::Null);
  }

  // For purposes of enforcing max_size_bytes, we intentionally don't care
  // that a VMO can only be a multiple of page size.

  uint64_t total_size_bytes = min_size_bytes * result->buffer_count;
  if (total_size_bytes > kMaxTotalSizeBytesPerCollection) {
    LogError("total_size_bytes > kMaxTotalSizeBytesPerCollection");
    *allocation_result = ZX_ERR_NO_MEMORY;
    return BufferCollection::BufferCollectionInfo(BufferCollection::BufferCollectionInfo::Null);
  }

  if (min_size_bytes > kMaxSizeBytesPerBuffer) {
    LogError("min_size_bytes > kMaxSizeBytesPerBuffer");
    *allocation_result = ZX_ERR_NO_MEMORY;
    return BufferCollection::BufferCollectionInfo(BufferCollection::BufferCollectionInfo::Null);
  }
  ZX_DEBUG_ASSERT(min_size_bytes <= std::numeric_limits<uint32_t>::max());

  // Now that min_size_bytes accounts for any ImageFormatConstraints, we can
  // just allocate min_size_bytes buffers.
  //
  // If an initiator (or a participant) wants to force buffers to be larger
  // than the size implied by minimum image dimensions, the initiator can use
  // BufferMemorySettings.min_size_bytes to force allocated buffers to be
  // large enough.
  buffer_settings->size_bytes = static_cast<uint32_t>(min_size_bytes);

  for (uint32_t i = 0; i < result->buffer_count; ++i) {
    // Assign directly into result to benefit from FidlStruct<> management
    // of handle lifetime.
    zx::vmo vmo;
    zx_status_t allocate_result = AllocateVmo(allocator, settings, &vmo);
    if (allocate_result != ZX_OK) {
      ZX_DEBUG_ASSERT(allocate_result == ZX_ERR_NO_MEMORY);
      LogError("AllocateVmo() failed - status: %d", allocate_result);
      // In release sanitize error code to ZX_ERR_NO_MEMORY regardless of
      // what AllocateVmo() returned.
      *allocation_result = ZX_ERR_NO_MEMORY;
      return BufferCollection::BufferCollectionInfo(BufferCollection::BufferCollectionInfo::Null);
    }
    // Transfer ownership from zx::vmo to FidlStruct<>.
    result->buffers[i].vmo = vmo.release();
  }

  // Register failure handler with memory allocator.
  allocator->AddDestroyCallback(reinterpret_cast<intptr_t>(this), [this]() {
    Fail("LogicalBufferCollection memory allocator gone - now auto-failing self.");
  });
  memory_allocator_ = allocator;

  ZX_DEBUG_ASSERT(*allocation_result == ZX_OK);
  return result;
}

zx_status_t LogicalBufferCollection::AllocateVmo(
    MemoryAllocator* allocator, const fuchsia_sysmem_SingleBufferSettings* settings,
    zx::vmo* child_vmo) {
  // raw_vmo may itself be a child VMO of an allocator's overall contig VMO,
  // but that's an internal detail of the allocator.  The ZERO_CHILDREN signal
  // will only be set when all direct _and indirect_ child VMOs are fully
  // gone (not just handles closed, but the kernel object is deleted, which
  // avoids races with handle close, and means there also aren't any
  // mappings left).
  zx::vmo raw_parent_vmo;
  zx_status_t status = allocator->Allocate(settings->buffer_settings.size_bytes, &raw_parent_vmo);
  if (status != ZX_OK) {
    LogError(
        "allocator->Allocate failed - size_bytes: %u "
        "status: %d",
        settings->buffer_settings.size_bytes, status);
    // sanitize to ZX_ERR_NO_MEMORY regardless of why.
    status = ZX_ERR_NO_MEMORY;
    return status;
  }

  zx_info_vmo_t info;
  status = raw_parent_vmo.get_info(ZX_INFO_VMO, &info, sizeof(info), nullptr, nullptr);
  if (status != ZX_OK) {
    LogError("raw_parent_vmo.get_info(ZX_INFO_VMO) failed - status %d", status);
    return status;
  }

  // Write zeroes to the VMO, so that the allocator doesn't need to.  Also flush those zeroes to
  // RAM so the newly-allocated VMO is fully zeroed in both RAM and CPU coherency domains.
  //
  // TODO(ZX-4817): Zero secure/protected VMOs.
  if (!allocator->CoherencyDomainIsInaccessible()) {
    uint64_t offset = 0;
    while (offset < info.size_bytes) {
      uint64_t bytes_to_write = std::min(sizeof(kZeroes), info.size_bytes - offset);
      status = raw_parent_vmo.write(kZeroes, offset, bytes_to_write);
      if (status != ZX_OK) {
        LogError("raw_parent_vmo.write() failed - status: %d", status);
        return status;
      }
      offset += bytes_to_write;
    }
    status = raw_parent_vmo.op_range(ZX_VMO_OP_CACHE_CLEAN, 0, info.size_bytes, nullptr, 0);
    if (status != ZX_OK) {
      LogError("raw_parent_vmo.op_range(ZX_VMO_OP_CACHE_CLEAN) failed - status: %d", status);
      return status;
    }
  }

  // We immediately create the ParentVmo instance so it can take care of calling allocator->Delete()
  // if this method returns early.  We intentionally don't emplace into parent_vmos_ until
  // StartWait() has succeeded.  In turn, StartWait() requires a child VMO to have been created
  // already (else ZX_VMO_ZERO_CHILDREN would trigger too soon).
  //
  // We need to keep the raw_parent_vmo around so we can wait for ZX_VMO_ZERO_CHILDREN, and so we
  // can call allocator->Delete(raw_parent_vmo).
  //
  // Until that happens, we can't let LogicalBufferCollection itself go away, because it needs to
  // stick around to tell allocator that the allocator's VMO can be deleted/reclaimed.
  //
  // We let cooked_parent_vmo go away before returning from this method, since it's only purpose
  // was to attenuate the rights of local_child_vmo.  The local_child_vmo counts as a child of
  // raw_parent_vmo for ZX_VMO_ZERO_CHILDREN.
  //
  // The fbl::WrapRefPtr(this) is fairly similar (in this usage) to shared_from_this().
  auto tracked_parent_vmo = std::unique_ptr<TrackedParentVmo>(new TrackedParentVmo(
      fbl::WrapRefPtr(this), std::move(raw_parent_vmo),
      [this, allocator](TrackedParentVmo* tracked_parent_vmo) mutable {
        auto node_handle = parent_vmos_.extract(tracked_parent_vmo->vmo().get());
        ZX_DEBUG_ASSERT(!node_handle || node_handle.mapped().get() == tracked_parent_vmo);
        allocator->Delete(tracked_parent_vmo->TakeVmo());
        // ~node_handle may delete "this".
      }));

  zx::vmo cooked_parent_vmo;
  status = tracked_parent_vmo->vmo().duplicate(kSysmemVmoRights, &cooked_parent_vmo);
  if (status != ZX_OK) {
    LogError("zx::object::duplicate() failed - status: %d", status);
    return status;
  }

  zx::vmo local_child_vmo;
  status = cooked_parent_vmo.create_child(ZX_VMO_CHILD_SLICE, 0,
                                          settings->buffer_settings.size_bytes, &local_child_vmo);
  if (status != ZX_OK) {
    LogError("zx::vmo::create_child() failed - status: %d", status);
    return status;
  }

  // Now that we know at least one child of raw_parent_vmo exists, we can StartWait() and add to
  // map.  From this point, ZX_VMO_ZERO_CHILDREN is the only way that allocator->Delete() gets
  // called.
  status = tracked_parent_vmo->StartWait();
  if (status != ZX_OK) {
    LogError("tracked_parent->StartWait() failed - status: %d", status);
    // ~tracked_parent_vmo calls allocator->Delete().
    return status;
  }
  zx_handle_t raw_parent_vmo_handle = tracked_parent_vmo->vmo().get();
  TrackedParentVmo& parent_vmo_ref = *tracked_parent_vmo;
  auto emplace_result = parent_vmos_.emplace(raw_parent_vmo_handle, std::move(tracked_parent_vmo));
  ZX_DEBUG_ASSERT(emplace_result.second);

  // Now inform the allocator about the child VMO before we return it.
  status = allocator->SetupChildVmo(parent_vmo_ref.vmo(), local_child_vmo);
  if (status != ZX_OK) {
    LogError("allocator->SetupChildVmo() failed - status: %d", status);
    // In this path, the ~local_child_vmo will async trigger parent_vmo_ref::OnZeroChildren()
    // which will call allocator->Delete() via above do_delete lambda passed to
    // ParentVmo::ParentVmo().
    return status;
  }

  *child_vmo = std::move(local_child_vmo);
  // ~cooked_parent_vmo is fine, since local_child_vmo counts as a child of raw_parent_vmo for
  // ZX_VMO_ZERO_CHILDREN purposes.
  return ZX_OK;
}

static int32_t clamp_difference(int32_t a, int32_t b) {
  int32_t raw_result = a - b;

  int32_t cooked_result = raw_result;
  if (cooked_result > 0) {
    cooked_result = 1;
  } else if (cooked_result < 0) {
    cooked_result = -1;
  }
  ZX_DEBUG_ASSERT(cooked_result == 0 || cooked_result == 1 || cooked_result == -1);
  return cooked_result;
}

// 1 means a > b, 0 means ==, -1 means a < b.
//
// TODO(dustingreen): Pay attention to constraints_->usage, by checking any
// overrides that prefer particular PixelFormat based on a usage / usage
// combination.
int32_t LogicalBufferCollection::CompareImageFormatConstraintsTieBreaker(
    const fuchsia_sysmem_ImageFormatConstraints* a,
    const fuchsia_sysmem_ImageFormatConstraints* b) {
  // If there's not any cost difference, fall back to choosing the
  // pixel_format that has the larger type enum value as a tie-breaker.

  int32_t result = clamp_difference(static_cast<int32_t>(a->pixel_format.type),
                                    static_cast<int32_t>(b->pixel_format.type));

  if (result != 0)
    return result;

  result = clamp_difference(static_cast<int32_t>(a->pixel_format.has_format_modifier),
                            static_cast<int32_t>(b->pixel_format.has_format_modifier));

  if (result != 0)
    return result;

  if (a->pixel_format.has_format_modifier && b->pixel_format.has_format_modifier) {
    result = clamp_difference(static_cast<int32_t>(a->pixel_format.format_modifier.value),
                              static_cast<int32_t>(b->pixel_format.format_modifier.value));
  }

  return result;
}

int32_t LogicalBufferCollection::CompareImageFormatConstraintsByIndex(uint32_t index_a,
                                                                      uint32_t index_b) {
  // This method is allowed to look at constraints_.
  ZX_DEBUG_ASSERT(constraints_);

  int32_t cost_compare = UsagePixelFormatCost::Compare(parent_device_->pdev_device_info_vid(),
                                                       parent_device_->pdev_device_info_pid(),
                                                       constraints_.get(), index_a, index_b);
  if (cost_compare != 0) {
    return cost_compare;
  }

  // If we get this far, there's no known reason to choose one PixelFormat
  // over another, so just pick one based on a tie-breaker that'll distinguish
  // between PixelFormat(s).

  int32_t tie_breaker_compare =
      CompareImageFormatConstraintsTieBreaker(&constraints_->image_format_constraints[index_a],
                                              &constraints_->image_format_constraints[index_b]);
  return tie_breaker_compare;
}

LogicalBufferCollection::TrackedParentVmo::TrackedParentVmo(
    fbl::RefPtr<LogicalBufferCollection> buffer_collection, zx::vmo vmo,
    LogicalBufferCollection::TrackedParentVmo::DoDelete do_delete)
    : buffer_collection_(std::move(buffer_collection)),
      vmo_(std::move(vmo)),
      do_delete_(std::move(do_delete)),
      zero_children_wait_(this, vmo_.get(), ZX_VMO_ZERO_CHILDREN) {
  ZX_DEBUG_ASSERT(buffer_collection_);
  ZX_DEBUG_ASSERT(vmo_);
  ZX_DEBUG_ASSERT(do_delete_);
}

LogicalBufferCollection::TrackedParentVmo::~TrackedParentVmo() {
  ZX_DEBUG_ASSERT(!waiting_);
  if (do_delete_) {
    do_delete_(this);
  }
}

zx_status_t LogicalBufferCollection::TrackedParentVmo::StartWait() {
  LogInfo("LogicalBufferCollection::TrackedParentVmo::StartWait()");
  // The current thread is the dispatcher thread.
  ZX_DEBUG_ASSERT(!waiting_);
  zx_status_t status = zero_children_wait_.Begin(async_get_default_dispatcher());
  if (status != ZX_OK) {
    LogError("zero_children_wait_.Begin() failed - status: %d", status);
    return status;
  }
  waiting_ = true;
  return ZX_OK;
}

zx::vmo LogicalBufferCollection::TrackedParentVmo::TakeVmo() {
  ZX_DEBUG_ASSERT(!waiting_);
  ZX_DEBUG_ASSERT(vmo_);
  return std::move(vmo_);
}

const zx::vmo& LogicalBufferCollection::TrackedParentVmo::vmo() const {
  ZX_DEBUG_ASSERT(vmo_);
  return vmo_;
}

void LogicalBufferCollection::TrackedParentVmo::OnZeroChildren(async_dispatcher_t* dispatcher,
                                                               async::WaitBase* wait,
                                                               zx_status_t status,
                                                               const zx_packet_signal_t* signal) {
  LogInfo("LogicalBufferCollection::TrackedParentVmo::OnZeroChildren()");
  ZX_DEBUG_ASSERT(waiting_);
  waiting_ = false;
  ZX_DEBUG_ASSERT(status == ZX_OK);
  ZX_DEBUG_ASSERT(signal->trigger & ZX_VMO_ZERO_CHILDREN);
  ZX_DEBUG_ASSERT(do_delete_);
  LogicalBufferCollection::TrackedParentVmo::DoDelete local_do_delete = std::move(do_delete_);
  ZX_DEBUG_ASSERT(!do_delete_);
  // will delete "this"
  local_do_delete(this);
  ZX_DEBUG_ASSERT(!local_do_delete);
}
